﻿using System;
using System.IO;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.X509;
using Netfraction.Common.Logging;

namespace NetfractionHub.Network.Protocol.ADCS.Security
{
	public class Certificate
	{
		const string CERT_FILE_NAME = "hubCertificate.crt";
		const string KEY_FILE_NAME = "hubKey.key";
		const int CERTIFICATE_LIFESPAN_DAYS = 3650;
		const int KEY_LENGTH = 2048;

		public static string GetCertificatePath()
		{
			Netfraction.Common.DataStorage.BinaryDataStore certStore = new Netfraction.Common.DataStorage.BinaryDataStore(NetfractionHub.Security.SecurityDataSource.Source, CERT_FILE_NAME);
			return certStore.FullPath;
		}

		public static string GetKeyPath()
		{
			Netfraction.Common.DataStorage.BinaryDataStore keyStore = new Netfraction.Common.DataStorage.BinaryDataStore(NetfractionHub.Security.SecurityDataSource.Source, KEY_FILE_NAME);
			return keyStore.FullPath;
		}

		public static System.Security.Cryptography.AsymmetricAlgorithm GetPrivateKey()
		{
			Org.BouncyCastle.OpenSsl.PemReader pemReader = new Org.BouncyCastle.OpenSsl.PemReader(new StreamReader(GetKeyPath()));
			Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair keyPair = null;

			object o;
			while ((o = pemReader.ReadObject()) != null)
			{
				if (o is Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair)
				{
					keyPair = (Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair)o;
				}
			}

			if (keyPair != null)
			{
				Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters keyParams = (Org.BouncyCastle.Crypto.Parameters.RsaPrivateCrtKeyParameters)keyPair.Private;
				System.Security.Cryptography.RSAParameters rsaParameters = Org.BouncyCastle.Security.DotNetUtilities.ToRSAParameters(keyParams);
				System.Security.Cryptography.CspParameters cspParameters = new System.Security.Cryptography.CspParameters();
				cspParameters.KeyContainerName = "MyKeyContainer";
				System.Security.Cryptography.RSACryptoServiceProvider rsaKey = new System.Security.Cryptography.RSACryptoServiceProvider(KEY_LENGTH, cspParameters);
				rsaKey.ImportParameters(rsaParameters);

				return rsaKey;
			}

			return null;
		}

		public static bool VerifyOrCreateCertificate()
		{
			Netfraction.Common.DataStorage.BinaryDataStore certStore = new Netfraction.Common.DataStorage.BinaryDataStore(NetfractionHub.Security.SecurityDataSource.Source, CERT_FILE_NAME);
			Netfraction.Common.DataStorage.BinaryDataStore keyStore = new Netfraction.Common.DataStorage.BinaryDataStore(NetfractionHub.Security.SecurityDataSource.Source, KEY_FILE_NAME);

			if (!File.Exists(certStore.FullPath) || !File.Exists(keyStore.FullPath))
				GenerateCertificates();

			//System.Security.Cryptography.X509Certificates.X509Certificate x = System.Security.Cryptography.X509Certificates.X509Certificate2.CreateFromCertFile(certStore.FullPath);
			//x.GetCertHashString
			return true;
		}

		public static void GenerateCertificates()
		{
			// Steps
			// 1. Generate the RSA Key Pair.
			// 2. Set the X.509 certificate members.
			// 3. Generate the X.509 certificate.
			// 4. Write the X.509 certificate to file.
			// 5. Write the RSA Private Key to file.

			X509Certificate x509;

			// 1. Generate the RSA Key Pair.
			SecureRandom secureRandom = new SecureRandom();
			BigInteger publicExponent = BigInteger.ValueOf(0x10001L); // 0x10001 = RSA_F4 from OpenSSL's rsa.h

			RsaKeyGenerationParameters rsaKeyGenParams = new RsaKeyGenerationParameters(publicExponent, secureRandom, KEY_LENGTH, 80);   // Not sure what certainty = 80 means :S
			RsaKeyPairGenerator rsaKeyPairGen = new RsaKeyPairGenerator();
			rsaKeyPairGen.Init(rsaKeyGenParams);

			AsymmetricCipherKeyPair keyPair = rsaKeyPairGen.GenerateKeyPair();
			
			// 2. Set the X.509 certificate members.
			X509V1CertificateGenerator xGen = new X509V1CertificateGenerator();
			BigInteger serialNum = BigInteger.ProbablePrime(64, new Random()); // Should generate something random here, at least 64-bits e.g. DateTime.Now.Ticks or System.Random
			X509Name xName = new X509Name(string.Format("CN={0}", string.Empty)); // Set the name to the user's client Id

			xGen.SetSerialNumber(serialNum);
			xGen.SetIssuerDN(xName);
			xGen.SetNotBefore(DateTime.Now);
			xGen.SetNotAfter(DateTime.Now.AddDays(CERTIFICATE_LIFESPAN_DAYS));
			xGen.SetSubjectDN(xName);

			xGen.SetPublicKey(keyPair.Public);
			xGen.SetSignatureAlgorithm("SHA1withRSA");
			
			// 3. Generate the X.509 certificate.
			x509 = xGen.Generate(keyPair.Private);

			// 4. Write the X.509 certificate to file.
			StreamWriter sWriter;
			PemWriter pWriter;

			Netfraction.Common.DataStorage.BinaryDataStore certStore = new Netfraction.Common.DataStorage.BinaryDataStore(NetfractionHub.Security.SecurityDataSource.Source, CERT_FILE_NAME);
			sWriter = new StreamWriter(new FileStream(certStore.FullPath, FileMode.Create));
			pWriter = new PemWriter(sWriter);
			pWriter.WriteObject(x509);
			sWriter.Flush();
			sWriter.Close();
			LogManager.WriteEntry(string.Format("ADCS Hub Certificate created at: {0}.", certStore.FullPath), LogEventId.TLSCertificateCreated, LogLevel.Basic);

			// 5. Write the RSA Private Key to file.
			Netfraction.Common.DataStorage.BinaryDataStore keyStore = new Netfraction.Common.DataStorage.BinaryDataStore(NetfractionHub.Security.SecurityDataSource.Source, KEY_FILE_NAME);
			sWriter = new StreamWriter(new FileStream(keyStore.FullPath, FileMode.Create));
			pWriter = new PemWriter(sWriter);
			pWriter.WriteObject(keyPair);
			sWriter.Flush();
			sWriter.Close();
			LogManager.WriteEntry(string.Format("ADCS Hub Certificate Private Key created at: {0}.", keyStore.FullPath), LogEventId.TLSCertificatePrivateKeyCreated, LogLevel.Basic);
		}
	}
}
